// 装饰器本质是一种特殊的函数，

/**
 * Demo函数会在Person类定义时执行
 * @param target 被装饰的类，即：Person
 */
function Demo(target:Function){
    console.log(target)
}

// 装饰器可以替换target的prototype的特定方法内容
function CustomString(target:Function){
    target.prototype.toString=function(){
        return JSON.stringify(this)
    }
    Object.seal(target.prototype) //密封该targeet，不能再添加属性
}

// @Demo //等价于调用 Demo(Person)
@CustomString
class Person{
    constructor(public name:string,public age:number){}
}

const p1=new Person('张',18)
console.log(p1.toString())


// 一旦装饰器有返回值,若返回值是一个类，则替换掉target类类型
function Demo2(target:Function){
    return class {
        test(){
            console.log(300)
        }
    }
}

@Demo2
class Person2{
    test(){
        console.log(100)
    }
}

console.log(Person2)

/**
 * new 表示：该类型是可以用new操作符调用
 * ...args 表示：构造器可以接受【任意数量】的参数
 * any[] 表示：构造器可以接受【任意类型】的参数
 * {} 表示：返回类型是对象（非null、非undefined）
 */
type Constructor=new (...args:any[])=>{}

console.log(typeof({}))
console.log(typeof(Object))

type Constructor2={
    new (...args:any[]):{}; //构造签名
    wife:string; // wife是一个静态属性
}

function test(fn:Constructor2){

}


// 实现一个需求
function LogTime<T extends Constructor>(target:T){
    return class extends target{
        createdTime:Date;
        constructor(...args:any[]){
            super(...args)
            this.createdTime=new Date()
        }
        getTime(){
            return `该对象的创建时间是：${this.createdTime}`
        }
    }
}

@LogTime
class PP{
    constructor(public name:string,public age:number){}
    speak(){
        console.log("你好")
    }
}

interface PP{
    getTime():void
}

const pp2=new PP("zh",123)
console.log(pp2)
console.log(pp2.getTime())

// 带参装饰器
function LogInfo(n:number){
    return function(target:Function){
        target.prototype.introduce=function(){
            for(let i=0;i<n;i++){
                console.log(`i:${i}`)
            }
        }
    }
}

@LogInfo(100) //LogInfor代表装饰器工厂，真正的装饰器由LogInfo返回值定义
class PP3{
    constructor(public name:string,public age:number){}
}

interface PP3{
    introduce():void
}

const pp3=new PP3("zh",123)
pp3.introduce()

// 装饰器组合
@CustomString
@LogInfo(5)
@LogTime
class P5{
    constructor(public name:string,public age:number){}
}

// 属性装饰器
/**
 * 参数说明：
 * @param target 对于静态属性来说值是类，对于实例属性来说，值是类的原型对象
 * @param propertyKey：属性名
 */
function Demo_pro(target:object,propertyKey:string){
    console.log("target:",target);
    console.log("pro:",propertyKey);
}

function State(target:object,propertyKey:string){
    // let value:any;
    let key=`__${propertyKey}`
    Object.defineProperty(target,propertyKey,{
        get(){
            return this[key]
        },
        set(newValue){
            console.log(`${propertyKey}的最新值为${newValue}`)
            // value=newValue
            this[key]=newValue
        }
    })
}

class P6{
    @Demo_pro name:string;
    @State age:number;
    @Demo_pro static school:string;

    constructor(name:string,age:number){
        this.name=name;
        this.age=age;
    } 
}

const p6=new P6("z",19)
p1.age=30

//方法装饰器
/**
 * 参数说明
 * @param target 对于静态方法来说值是类，对于实例方法来说值是原型对象
 * @param propertyKey 方法的名称
 * @param descriptor 方法的描述对象，其中value属性是被装饰的方法
 */
function Demo_method(target:any,propertyKey:any,descriptor:any){
    console.log(target)
    console.log(propertyKey)
    console.log(descriptor)
}

class P7{
    name:string;
    age:number;
    static school:string;

    constructor(name:string,age:number){
        this.name=name;
        this.age=age;
    } 

    @Demo_method
    speak(){
        console.log(`你好，我的名字：${this.name}`)
    }

    // @Demo_method
    static isAdult(age:number){
        return age>=18;
    }
}


// 方法装饰器
function Logger(target:object,propertyKey:string,descriptor:PropertyDescriptor){
    // 存储原始方法
    const originnal=descriptor.value;
    // 替换原始方法
    descriptor.value=function(...args:any[]){
        console.log(`${propertyKey}开始执行...`)
        // 执行原始函数
        const result=originnal.call(this,...args)
        console.log(`${propertyKey}执行完毕...`)
        return result;
    }
}

class P8{
    constructor(public name:string,public age:number){}
    @Logger speak(){
        console.log(`你好，我的名字：${this.name},年龄:${this.age}`)
    }
}

const p8=new P8('tom',18)
p8.speak()